<?php

namespace common\service;

use common\model\DeadlineTaskModel;
use support\bootstrap\Redis;
use support\SingletonTrait;

class DeadlineTaskService
{
    use SingletonTrait;

    const MAX_RETRY_TIME = 3;   //最大重试次数
    const MAX_TIME_OUT = 300;   //多久没完成算超时

    /**
     * redis list key名
     * @var string
     */
    protected $redisListKey = '';

    /**
     * redis hash key名
     * @var string
     */
    protected $redisHasHKey = '';

    /**
     * 租户ID
     * @var int
     */
    protected $tenantId = 0;
    protected $redis = '';

    public function __construct(int $tenantId)
    {
        $this->tenantId = $tenantId;
        //
        $this->redisListKey = 'tenant_deadline_list_' . $tenantId;
        $this->redisHasHKey = 'tenant_deadline_hash_' . $tenantId;

        $this->redis = new Redis();
    }

    /**
     * 创建任务消息
     * @param array  $eventData
     * @param string $jobId
     * @return array
     */
    public function push(array $eventData = [], string $jobId = "")
    {
        if (empty($eventData)) {
            return [];
        }
        //任务消息  类型
        $eventType = $eventData['type'] ?? '';
        $json = $eventData['json'] ?? [];
        try {
            //保存到 数据库 和redis
            $id = $this->pushDb($eventType, $json, $jobId);
            $this->pushRedis($eventType, $json, $id);
        } catch (\Throwable $e) {
            trace('创建任务消息失败' . format_exception_info($e), 'ERR');
        }
        return [];
    }


    /**
     * 取出一条消息
     * @return array
     */
    public function pop()
    {
        //更新redis
        $this->updateDbToRedis();

        $data = $this->pullData();
        if (empty($data)) {
            return [];
        }
        list($messageId, $messageData) = $data;
        $list = json_decode($messageData, true);
        $json = $list['json'] ?? [];

        return [
            'id' => intval($messageId),
            'type' => $list['type'] ?? '',
            'json' => $json
        ];
    }

    /**
     * 获取消息
     * @return array|null
     */
    private function pullData()
    {
        // 弹出右侧队首
        $k = $this->redis::rPop($this->redisListKey);
        if (empty($k)) {
            //从数据库更新
            return null;
        }
        // 取出data
        $v = $this->redis::hGet($this->redisHasHKey, $k);
        //更新数据库状态
        try {
            model(DeadlineTaskModel::class)->where(['id' => $k])->save(['status' => DeadlineTaskModel::STATUS_PRE_START, 'updated' => time()]);
        } catch (\Throwable $e) {
            trace('数据未找到' . format_exception_info($e), 'ERR');
        }
        return [$k, $v];
    }


    /**
     * 修改任务
     * @param $data
     * @return int
     */
    public function updateDeadLine($data)
    {
        // 删除data redis
        $this->redis::hDel($this->redisHasHKey, $data['id']);
        $updateData = [];


        //更新job_id  任务变成进行中
        if (isset($data['job_id'])) {
            $updateData['job_id'] = $data['job_id'];
            $updateData['status'] = DeadlineTaskModel::STATUS_BEGIN;
        }
        if (isset($data['status'])) {
            $updateData['status'] = $data['status'];
            //完成时间
            if ($data['status'] == DeadlineTaskModel::STATUS_DONE) {
                $updateData['endtime'] = time();
            }
        }

        //更新数据库
        try {
            model(DeadlineTaskModel::class)->where(['id' => $data['id']])->save($updateData);
        } catch (\Throwable $e) {
            trace('数据未找到' . format_exception_info($e), 'ERR');
        }
        return 1;
    }


    /**
     * 更新超时数据  到redis
     * @return int
     */
    private function updateDbToRedis()
    {
        //超时判定时间
        $outTime = time() - self::MAX_TIME_OUT;
        $deadlineModel = model(DeadlineTaskModel::class);
        //没job_id  超时
        $info = $deadlineModel
            ->where([
                'tenant_id' => $this->tenantId,
                'updated' => ['lt', $outTime],
                'job_id' => '',
                'status' => DeadlineTaskModel::STATUS_PRE_START
            ])
            ->find();
        if (empty($info)) {
            return 1;
        }

        try {
            //更新超时的数据到redis
            $deadlineModel->where(['id' => $info['id']])->save(['status' => DeadlineTaskModel::STATUS_RETRY, 'updated'=>time() ]);
            $this->pushRedis($info['type'], json_decode($info['json'], true), $info['id']);
        } catch (\Throwable $e) {
            trace('更新超时数据失败' . format_exception_info($e), 'ERR');
        }
        return 1;
    }


    /**
     * 存入数据库
     * @param string   $type      类型
     * @param array $eventData 数据
     * @return int
     * @throws \Exception
     */
    private function pushDb(string $type, array $eventData, string $jobId)
    {
        $deadlineModel = model(DeadlineTaskModel::class);
        $data = [];
        $data['tenant_id'] = $this->tenantId;
        $data['type'] = $type;
        $data['json'] = json_encode($eventData);
        $rt = $deadlineModel->addItem($data);

        return $rt['id'] ?? 0;
    }

    /**
     * 存入redis
     * @param string    $type      类型
     * @param array  $eventData 数据
     * @param string $messageId 唯一标识
     * @return void
     */
    private function pushRedis(string $type, array $eventData, $messageId)
    {
        $data = [
            'type' => $type,
            'json' => $eventData,
        ];
        // redis队例
        $this->redis::lPush($this->redisListKey, $messageId);
        // 写入 data
        $this->redis::hSet($this->redisHasHKey, $messageId, json_encode($data));
    }


    /**
     * 查询进行中的任务
     * @return array|false|mixed|string
     */
    public function deadlineTaskList()
    {

        $where = [];
        //查询条件
        $where['tenant_id'] = $this->tenantId;
        //默认查询时行中
        $where['status'] = DeadlineTaskModel::STATUS_BEGIN;
        $where['job_id'] = ['NEQ', ''];

        $list = model(DeadlineTaskModel::class)
            ->where($where)
            ->limit(100)
            ->select();
        foreach ($list as &$val) {
            $val['json'] = json_decode($val['json'], true);
        }

        return $list;
    }

}
